文章目录
  1. 极光
    1. 批量操作
      1. 按复杂条件修改
      2. 按复杂条件删除
      3. 批量新增
      4. 批量更新
    2. 索引
    3. 代理属性
    4. 打印日志
  2. 清明
    1. 支持更复杂的外键关系
  3. 阳春
    1. customTypeHandler
  4. 初雪
    1. 自定义主键生成器
    2. 使用或逻辑查询
      1. 普通或逻辑查询
      2. 外键或逻辑查询
    3. 优化使用体验
    4. 全面提升代码质量

极光

对应 flying 版本号为 1.0.0(适用于 mybatis-3.4.x),主要新增特性如下:

批量操作

按复杂条件修改

1.0.0 开始加入按复杂条件修改操作。因为批量操作的入参要同时描述修改后的状态和参与修改的条件(在修改单条记录时我们只匹配主键),所以此场景下我们采用 pojo_condition 类作为入参。pojo_condition 类是 pojo 类的子类,无需任何改动它就可以完成描述修改后的状态的任务;同时它又可以按需要加入各种 @ConditionMapperAnnotation 注解,以前版本修改操作会忽略这些注解,但从此版本开始这些注解会描述批量修改的条件,它们可以是复杂的组合,如 <>likeor 等,只要您的数据库支持即可。仍以上一篇文章的AccountCondition为例,在AccountCondition中加入以下属性:

1
2
3
4
@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.Equals)
/*用作 name 完全匹配的值*/
private String nameEquals;
/*相关的getter和setter方法请自行补充*/

上面的代码看似和 Account 原有的 name 属性功能重复,但实际上因为它有 @ConditionMapperAnnotation 注解,在 update 操作时可以作为批量处理条件:

1
2
3
4
5
AccountCondition ac = new AccountCondition();
ac.setNameEqual("ann");
ac.setName("bob");
/*将所有姓名为 ann 的用户都更名为 bob*/
accountService.update(ac);

以上只是最简单的批量修改,当您理解这个机制后就可以使用复杂的条件组合,flying 完全支持。

最后,updatePersist 操作不支持批量执行,因为它的特点是更新全部字段,批量处理不会得到您想要的结果,所以在 updatePersist 操作中入参的 @ConditionMapperAnnotation 注解仍然会被忽略。

按复杂条件删除

1.0.0 开始加入按复杂条件删除操作。与按复杂条件修改类似,按复杂条件删除的入参要描述删除条件(在删除单条记录时我们只匹配主键),所以我们仍然采用 pojo_condition 类作为入参。pojo_condition 类是 pojo 类的子类,无需任何改动它就可以完成描述修改后的状态的任务;同时它又可以按需要加入各种 @ConditionMapperAnnotation 注解,以前版本删除操作会忽略这些注解,但从此版本开始这些注解会描述批量删除的条件,它们可以是复杂的组合,如 <>likeor 等,只要您的数据库支持即可。仍以上一篇文章的AccountCondition为例,在AccountCondition中加入以下属性:

1
2
3
4
@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.Equals)
/*用作 name 完全匹配的值*/
private String nameEquals;
/*相关的getter和setter方法请自行补充*/

上面的代码看似和 Account 原有的 name 属性功能重复,但实际上因为它有 @ConditionMapperAnnotation 注解,在 delete 操作时可以作为批量处理条件:

1
2
3
4
AccountCondition ac = new AccountCondition();
ac.setNameEqual("ann");
/*将所有姓名为 ann 的用户都删除*/
accountService.delete(ac);

以上只是最简单的按复杂条件删除,当您理解这个机制后就可以使用复杂的条件组合,flying 完全支持。

批量新增

1.0.0 开始加入批量新增操作。批量新增使用的 “action” 为 "insertBatch",如下:

1
2
3
<insert id="insertBatch">
{"action":"insertBatch", "keyGenerator":"uuid"}
</insert>

对应的 mapper 方法为(注意方法的入参):

1
public void insertBatch(Collection<Account> c);

使用方法为:

1
2
3
4
5
6
7
Collection<Account> c = new LinkedList<>();
Account a1 = new Account(), a2 = new Account();
a1.setName("ann");
c.add(a1);
a2.setName("bob");
c.add(a2);
accountMapper.insertBatch(c);

这样一来就能以批量新增的方式增加两条数据(数据库只连接一次)。

在批量新增时不建议使用 mybatis 提供的自增主键方式,建议使用 flying-json 中的 "keyGenerator" 属性指定主键生成方式。

批量更新

1.0.1 开始加入批量更新操作。批量更新使用的 “action” 为 "updateBatch",如下:

1
2
3
<update id="updateBatch">
{"action":"updateBatch"}
</update>

对应的 mapper 方法为(注意方法的入参):

1
public void updateBatch(Collection<Account> c);

使用方法为:

1
2
3
4
5
6
7
8
9
Collection<Account> c = new LinkedList<>();
Account a1 = new Account(), a2 = new Account();
a1.setId(1);
a1.setName("ann");
c.add(a1);
a2.setId(2);
a2.setName("bob");
c.add(a2);
accountMapper.updateBatch(c);

这样一来就能以批量更新的方式更新两条数据(数据库只连接一次)。

索引

1.0.0 开始查询和计数操作可以使用索引,使用方法为在 json 的 "index" 属性中添加内容,例如 account 表的查询方法如下:

1
2
3
<select id="selectUseIndex" resultMap="result">
{"action":"select#{?}", "index":"use index(index1)"}
</select>

则生成的 sql 语句为:

1
select ... from account use index(index1) where account.id = ...

您在 "index" 中输入的内容会正确的出现在索引的位置,除 “use index” 外您也可以使用 “force index” 和 “ignore index” 等。

代理属性

1.0.0 开始对于关联对象的主键增加了一个代理属性,这是出于某些情况下优化性能的考虑。例如 Account 类中有一个关联属性 Role,如下:

1
2
3
@FieldMapperAnnotation(dbFieldName = "role_id", jdbcType = JdbcType.INTEGER, dbAssociationUniqueKey = "id")
private Role_ role;
/*相关的getter和setter方法请自行补充*/

如果我们想查询 role_id 为 1 的结果集,以前我们需要这样做:

1
2
3
4
5
Account a = new Account();
Role r = new Role();
r.setId(1);
a.setRole(r);
accountMapper.selectAll(a);

为了避免只查询一个外键却要创建整个对象的开销,我们引入了代理机制,在 Account 中增加如下代码:

1
2
3
@FieldMapperAnnotation(dbFieldName = "role_id", jdbcType = JdbcType.INTEGER, delegate = true)
private Integer delegateRoleId;
/*相关的getter和setter方法请自行补充*/

如果我们想查询 role_id 为 1 的结果集,现在我们只需这样做:

1
2
3
Account a = new Account();
a.setDelegateRoleId(1);
accountMapper.selectAll(a);

避免了创建新对象,自然也就提高了性能。

目前代理特性只适合于外键场景,并且建议您把这个代理变量的命名和被它代理的字段明显区分开(如名字前面统一加上 “delegate”),这可以防止您不使用代理属性时它却被错误的赋予了值,例如 mybatis 的自动 orm 机制。

打印日志

1.0.0 开始加入打印生成的 sql 语句功能,方法为在 flying 的 plugin 配置中加入名为 “logLevel” 的 property,如下:

1
2
3
4
5
<plugin interceptor="indi.mybatis.flying.interceptors.AutoMapperInterceptor">
<property name="dialect" value="mysql" />
<!-- 对自动生成的sql的打印级别,可接受none、trace、debug、warn、error,默认为none,即不打印日志 -->
<property name="logLevel" value="error" />
</plugin>

当开启后 flying 在执行每一次操作时都会在指定级别进行打印,当然是否显示还要看您的日志文件配置。打印日志的格式如下:

1
Auto generated sql:select ...

清明

对应 flying 版本号为 0.9.9(适用于 mybatis-3.4.x),主要新增特性如下:

支持更复杂的外键关系

在注解 @FieldMapperAnnotation 中加入 类型为 @ForeignAssociation[] 的新属性 associationExtra(),例如为实现 ‘a left join b on (a.f_id = b.id and a.name_a = b.name_b and a.version_a >= b.version_b and …)’,您可以使用如下代码:

1
2
3
@FieldMapperAnnotation(dbFieldName = "f_id", jdbcType = JdbcType.Integer, dbAssociationUniqueKey = "id", associationExtra = {
@ForeignAssociation(dbFieldName = "name_a", dbAssociationFieldName = "name_b"),
@ForeignAssociation(dbFieldName = "version_a", dbAssociationFieldName = "version_b" ,condition=AssociationCondition.GreaterOrEqual) })

同时在默认左外连接的基础上,增加了右外连接,实现方式为在注解 @FieldMapperAnnotation 中加入 新属性 associationType(),例如为实现一个右外连接您可以使用如下代码:

1
@FieldMapperAnnotation(dbFieldName = "f_id", jdbcType = JdbcType.Integer, dbAssociationUniqueKey = "id", associationType = AssociationType.RightJoin)

阳春

对应 flying 版本号为 0.9.4(适用于 mybatis-3.4.x),主要新增特性如下:

customTypeHandler

0.9.4 开始 @FieldMapperAnnotation@ConditionMapperAnnotation 增加了 customTypeHandler 属性,使您可以用自定义的 TypeHandler 来处理变量映射,因为 customTypeHandler 具有最高优先级。

初雪

对应 flying 版本号为 0.8.3(适用于 mybatis-3.3.x)和 0.9.3(适用于 mybatis-3.4.x),主要新增特性如下:

自定义主键生成器

为满足个性化主键需要,flying 新增了自定义主键生成器,为此我们在 flying-json 中新增了 “keyGenerator” 属性,如下:

1
2
3
"keyGenerator":"uuid"                      使用标准uuid作主键
"keyGenerator":"uuid_no_line" 使用无下横线的uuid作主键
"keyGenerator":"millisecond" 使用毫秒数作主键(需保证每秒并发在1000以下)

当然更多的情况是您会自定义自己的主键生成器,只要您的主键生成器实现了 flying 中的 indi.mybatis.flying.type.KeyHandler 接口即可,比如这样调用一个自定义的主键生成器类:

1
"keyGenerator":"indi.mybatis.flying.handlers.MySnowFlakeKeyHandler"

(上面的 indi.mybatis.flying.handlers.MySnowFlakeKeyHandler 是一个雪花主键生成器的 java 版本实现。雪花主键生成器由 tweeter 发明用于处理大规模并行写入,主键采用 float 类型存储以节省资源,自带递增无需 order by,单台主机每秒可产生 400 万个不同主键,最多可 1024 台主机集群同时工作。flying 中提供了一个实现方式,代码见此

在某些特殊业务场景中,您需要使用主键表达一定的业务含义,这时自定义主键生成器会显得非常有效。

使用或逻辑查询

普通或逻辑查询

“或”逻辑查询是 初雪 版本新增的最重要内容。为了实现此特性 flying 新增了 Or 标签类(代码见此),这个标签的内容是ConditionMapperAnnotation标签的数组,所以在查询条件类中可以有如下标签代码:

1
2
3
4
5
@Or({
@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike),
@ConditionMapperAnnotation(dbFieldName = "age", conditionType = ConditionType.Equal),
@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike)
})

(上面是实现 name like ‘XXX%’ or age = ‘YYY’ or name like ‘ZZZ%’ 查询的条件)

同时为了赋值方便,我们采用Object数组的不定参数形式作为变量类型,于是整个代码变成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Or({
@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike),
@ConditionMapperAnnotation(dbFieldName = "age", conditionType = ConditionType.Equal),
@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.HeadLike)
})
private Object[] condition1;

public Object[] getCondition1 () {
return condition1;
}
public void setCondition1 (Object... condition1) {
this. condition1 = condition1;
}

如果我们描述 “name like ‘张%’ or age = 27 or name like ‘李%’ “,代码如下:

1
2
personCondition.setCondition1("张", 27, "李");
/* 注意参数顺序和 condition1 上 @ConditionMapperOrAnnotation 的内部顺序一致 */

您之前掌握的绝大部分 ConditionMapperAnnotation 都可以写在 @Or 中,只有本身就是集合型的条件类型例外,以下列出不能进入 @Or 的类型:

MultiLikeANDMultiLikeORInNotIn

除此之外的查询条件均可以参与或查询。

外键或逻辑查询

flying 在同库跨表查询时也可以做不同表上条件的或逻辑查询,比如我们要实现 person.name = ‘XXX’ or role.name = ‘YYY’ 查询,其中 role 是 person 业务上的父对象。我们可以在 role 的条件类中加入如下变量:

1
2
3
4
5
6
7
8
9
10
11
@Or({
@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.Equal),
@ConditionMapperAnnotation(dbFieldName = "name", conditionType = ConditionType.Equal, subTarget = mypackage.Person.class) })
private Object[] roleNameEqualsOrPersonNameEquals;

public Object[] getRoleNameEqualsOrPersonNameEquals () {
return roleNameEqualsOrPersonNameEquals;
}
public void setRoleNameEqualsOrPersonNameEquals (Object... roleNameEqualsOrPersonNameEquals) {
this. roleNameEqualsOrPersonNameEquals = roleNameEqualsOrPersonNameEquals;
}

上面的代码中第一行 ConditionMapperAnnotation 指的是 role 表,第二行指的是 person 表,因为是由 role 指向 person,所以第二行出现了 subTarget 参数用来引导路径,它的值就是业务上子对象的类路径。

值得注意的是,外键或逻辑查询中,跨表的 @Or 条件永远要写在业务上的父对象里,这是考虑到从子对象上寻找父对象并非唯一(例如多重外键情况,一个 person 有多个 role 型父对象,分别表示主要角色和次要角色等),然而从父对象上寻找子对象永远是唯一的。

如果您要查询用户名为“张三”或角色名为“wfadmin”的用户时,您只需这样做:

1
2
3
4
5
RoleConditon rc = new RoleCondition();
rc.setRoleNameEqualsOrPersonNameEquals("wfadmin","张三");
Person p = new Person();
p.setRole(rc);
Person<Collection> persons = personService.selectAll(p);

无论 role 是 person 业务上的直接父对象还是间接父对象都可以这样查询。

优化使用体验

@QueryMapperAnnotation 现在可以省略,只要您的某个类既继承实体 pojo 又实现 Conditionable 接口 flying 就可以判断出它是相关 pojo 的条件类。当然这个标签出现在代码里也没有任何问题。

全面提升代码质量

初雪 中,我们把代码质量作为提升的重要一环,同时使用 阿里 P3C 和 SonarQube 来修正代码缺陷,当您阅读 flying 源码时您会深切感受到这一点。

文章目录
  1. 极光
    1. 批量操作
      1. 按复杂条件修改
      2. 按复杂条件删除
      3. 批量新增
      4. 批量更新
    2. 索引
    3. 代理属性
    4. 打印日志
  2. 清明
    1. 支持更复杂的外键关系
  3. 阳春
    1. customTypeHandler
  4. 初雪
    1. 自定义主键生成器
    2. 使用或逻辑查询
      1. 普通或逻辑查询
      2. 外键或逻辑查询
    3. 优化使用体验
    4. 全面提升代码质量